package com.medusa.aps.business.modules.board.task;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.StrPool;
import com.alibaba.fastjson2.JSON;
import com.medusa.aps.business.model.constant.RedisConstants;
import com.medusa.aps.business.modules.board.model.output.WorkshopProductionEquipmentVO;
import com.medusa.aps.business.modules.board.mp.entity.WorkshopProductionStatistics;
import com.medusa.aps.business.modules.board.mp.service.IWorkshopProductionService;
import com.medusa.aps.business.modules.plan.mp.entity.ProcessDetails;
import com.medusa.aps.business.modules.plan.mp.service.ProcessDetailsService;
import com.medusa.aps.business.util.DateTimeUtils;
import com.medusa.aps.business.util.WorkshopStatUtils;
import com.medusa.aps.business.common.mp.model.SqlHelper;
import com.medusa.aps.business.common.redis.annotation.Redisson;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>车间生产统计定时任务</p>
 * @author An.Yan
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class WorkshopProductionStatisticsTask {


    private final int PAGE_SIZE = 1000;

    private final ProcessDetailsService processDetailsService;

    private final IWorkshopProductionService workshopProductionService;

    /**
     * <p>车间生产任务统计.对于原始数据进行分页统计,统计逻辑如下:</p>
     * <ul>
     *     <li>数据来源: process_detail表, 条件 NOW() <= planned_start_time OR (planned_start_time <= NOW() AND NOW <= planned_end_time),
     *     此处数据记为:sourceDataList </li>
     *     <li>
     *          根据数据来源构造一个key:车间ID, value:与车间ID匹配的所有排程明细集合的Map -> workshopAndProcessDetailListMap
     *     </li>
     *     <li>以车间为维度,遍历该车间所有排程明细数据,按照下面的逻辑进行计算</li>
     *     <ul>
     *         <li>以天为单位进行数据统计:</li>
     *             <ul>
     *                 <li>
     *                     已排产设备数量: 排程明细表中workshopId=当前车间,并且统计时间位于[plan_start_time, plan_end_time],且不重复的设备ID的数量
     *                 </li>
     *                 <li>
     *                     未排产设备数量: 统计当天系统所有可用设备数量减去当前车间当天已排产设备数量
     *                 </li>
     *                 <li>
     *                     计划数量: 参考{@link WorkshopStatUtils#calculatePlannedCnt(BigDecimal, Long, Long)}
     *                 </li>
     *                 <li>
     *                     已完工数量: 当日已完工数量如果大于等于当日计划数量,则已完工数量等于当日计划数量;否则等于它本身
     *                 </li>
     *                 <li>
     *                     生产进度: 已完工数量 / 计划数量 * 100
     *                 </li>
     *                 <li>关联订单数：</li>
     *                 <li>日期: statsDate</li>
     *             </ul>
     *     </ul>
     *     <li>
     *         遍历statMap,归并相关字段
     *     </li>
     * </ul>
     * <p>最后将统计完毕的数据保存在statsList中, 以待最终落地到workshop_production_statistics表中</p>
     */
    @Redisson(name = RedisConstants.WORKSHOP_PRODUCTION_LOCK_KEY, waitTime = -1)
    @Scheduled(cron = "0 30 2 * * *")
    public void statistics() {
        log.info("开始执行车间生产统计定时任务");
        try {
            LocalDate statDate = LocalDate.now();
            Long totalRecord = processDetailsService.lambdaQuery()
                    /*.ge(ProcessDetails::getPlannedStartTime, statDate)
                    .or(qw -> qw.le(ProcessDetails :: getPlannedStartTime, statDate).ge(ProcessDetails :: getPlannedEndTime, statDate))*/
                    .count();

            // 当数据为空,则等于零,统计可以执行后续补偿逻辑
            if (totalRecord == null || totalRecord < 0) {
                log.info("暂无排程明细数据");
                return;
            }
            // 统计数据集合
            List<WorkshopProductionStatistics> statsList = new ArrayList<>();
            if (totalRecord > PAGE_SIZE) {
                Long totalPage = totalRecord % PAGE_SIZE == 0 ? totalRecord / PAGE_SIZE : totalRecord / PAGE_SIZE + 1;
                for (int currPage = 1; currPage <= totalPage; currPage++) {
                    pagingStatistics(statsList, currPage, PAGE_SIZE);
                }
            } else {
                pagingStatistics(statsList, 1, totalRecord.intValue());
            }
            // 保存统计的数据
            workshopProductionService.saveStatisticsData(statsList);
        } catch (Exception e) {
            log.error("车间生产统计定时任务发生异常,异常信息: {}", JSON.toJSONString(e));
            throw new RuntimeException(e);
        }
        log.info("车间生产统计定时任务执行结束");
    }

    /**
     * 分页对排程明细数据进行统计
     * @param statisticsEntities 统计集合
     * @param currPage 当前页
     * @param pageSize 每页条数
     */
    private void pagingStatistics(List<WorkshopProductionStatistics> statisticsEntities, int currPage, int pageSize) {
        // 获取统计源数据
        List<ProcessDetails> sourceDateList = processDetailsService.lambdaQuery()
                .isNotNull(ProcessDetails::getWorkshopId)
                //.ge(ProcessDetails::getPlannedStartTime, currDate)
                //.or(qw -> qw.le(ProcessDetails :: getPlannedStartTime, currDate).ge(ProcessDetails :: getPlannedEndTime, currDate))
                .last(SqlHelper.limit(currPage, pageSize))
                .list();
        if (CollUtil.isEmpty(sourceDateList)) {
            log.info("sourceDateList为空, currPage = {}, pageSize = {}", currPage, pageSize);
            return;
        }

        // key:车间ID, value:与车间ID匹配的所有排程明细集合
        Map<Integer, List<ProcessDetails>> workshopAndProcessDetailListMap = sourceDateList.stream().collect(Collectors.groupingBy(ProcessDetails::getWorkshopId));

        // 统计Map, key:车间ID+统计日期, value:对应的统计对象集合
        SortedMap<String, List<WorkshopProductionStatistics>> statMap = new TreeMap<>();

        // 系统可用的设备总数
        Map<Integer, Long> availableEquipmentMap = this.workshopProductionService.statisticsAvailableEquipment(null);

        // 车间订单数量Map, key:车间ID+统计日期, value:对应的订单号Set
        Map<String, Set<String>> workshopOrderCntMap = new HashMap<>();

        // 车间已排产设备Map, key:车间ID+统计日期, value:与key匹配的不重复的设备ID集合
        SortedMap<String, Set<Long>> scheduledEquipmentMap = new TreeMap<>();

        workshopAndProcessDetailListMap.forEach((workshopID, processDetailList) -> {

            // 遍历排程明细集合,根据时间段分别统计数据
            processDetailList.forEach(processDetail -> {

                SortedMap<LocalDate, Duration> durationSortedMap = DateTimeUtils.strToDurationMap(processDetail.getSpecificTime());

                // 排程明细中时间段累计分钟
                Long totalMinutes = durationSortedMap.values().stream().mapToLong(e -> e.toMinutes()).sum();

                // 累计的计划数量
                BigDecimal totalPlannedCnt = new BigDecimal(0);

                // 以天为单位,计算相关统计字段
                for (Iterator<LocalDate> iterator = durationSortedMap.keySet().iterator(); iterator.hasNext();) {
                    LocalDate statDate = iterator.next();
                    Duration duration = durationSortedMap.get(statDate);
                    String key = workshopID + StrPool.UNDERLINE + statDate;
                    Integer availableEquipmentCnt = availableEquipmentMap.getOrDefault(workshopID, 0L).intValue();
                    List<WorkshopProductionEquipmentVO> equipmentVO = Optional.ofNullable(
                            this.workshopProductionService.statisticsEquipment(workshopID, statDate)).orElse(new ArrayList<>());
                    scheduledEquipmentMap.computeIfAbsent(key, k -> new HashSet<>())
                            .addAll(equipmentVO.stream().map(e -> e.getEquipmentId()).collect(Collectors.toSet()));
                    WorkshopProductionStatistics statEntity  = new WorkshopProductionStatistics()
                            .setWorkshopId(workshopID)
                            .setStatisticsDate(statDate)
                            .setUnscheduledEquipmentCnt(new BigDecimal(availableEquipmentCnt));
                    calculateStatisticsFields(statEntity, processDetail, duration, totalMinutes);
                    if (iterator.hasNext()) {
                        totalPlannedCnt = totalPlannedCnt.add(statEntity.getPlannedCnt());
                    } else {
                        BigDecimal plannedQuantity = processDetail.getPlannedQuantity();
                        BigDecimal plannedCnt = plannedQuantity.compareTo(totalPlannedCnt) >= 0 ? plannedQuantity.subtract(totalPlannedCnt) : new BigDecimal(0);
                        statEntity.setPlannedCnt(plannedCnt);
                    }
                    statMap.computeIfAbsent(key, k -> new ArrayList<>()).add(statEntity);
                    workshopOrderCntMap.computeIfAbsent(key, k -> new HashSet<>()).add(processDetail.getOrderNumber());

                }
            });
        });

        // 归并相关字段
        statMap.forEach((workshopIdAndStatDate, statList) -> {

            Optional<WorkshopProductionStatistics> optionalStat = statList.stream().reduce((x, y) -> {
                x.setPlannedCnt(x.getPlannedCnt().add(y.getPlannedCnt()));
                return x;
            });
            if (optionalStat.isPresent()) {
                // 计算生产进度 & 关联订单数量
                WorkshopProductionStatistics statItem = optionalStat.get();
                statItem.setAssociatedOrderCnt(workshopOrderCntMap.getOrDefault(workshopIdAndStatDate, new HashSet<>()).size());

                // 计算已排产 & 未排产设备数量

                Integer scheduledEquipmentCnt = scheduledEquipmentMap.getOrDefault(workshopIdAndStatDate, new HashSet<>()).size();
                statItem.setScheduledEquipmentCnt(new BigDecimal(scheduledEquipmentCnt));
                statItem.setUnscheduledEquipmentCnt(statItem.getUnscheduledEquipmentCnt().subtract(new BigDecimal(scheduledEquipmentCnt)));
                statisticsEntities.add(statItem);
            }
        });
    }

    /**
     * 计算相关统计字段
     * @param statisticsEntity {@link WorkshopProductionStatistics}
     * @param detail {@link ProcessDetails}
     */
    private void calculateStatisticsFields(WorkshopProductionStatistics statisticsEntity, ProcessDetails detail,
                                           Duration currDuration, Long totalMinutes) {

        if (currDuration == null) {
            statisticsEntity.setPlannedCnt(new BigDecimal(0));
            return;
        }

        // 当日,排程明细中工序所需分钟
        Long requiredMinutes = currDuration.toMinutes();

        // 计算当日计划数量
        BigDecimal plannedCnt = WorkshopStatUtils.calculatePlannedCnt(detail.getPlannedQuantity(),
                totalMinutes, requiredMinutes);
        statisticsEntity.setPlannedCnt(plannedCnt);
    }
}
